package r25.console;

import java.io.Console;
import java.util.ArrayList;
import java.util.List;

import r25.ServiceFactory;
import r25.board.ControlMove;
import r25.board.RoomPosition;
import r25.character.ICharacter;
import r25.engine.CharacterActionResponse;
import r25.engine.IEvent;
import r25.engine.IGameInstance;
import r25.engine.IGameInstance.CHARACTER_ACTION;
import r25.engine.IGameService.GAME_MODE;
import r25.room.IRoom;
import r25.tools.LoggerHelper;

public enum Command {
	BYE(new Action() {
		@Override
		public void exec(Console c, List<String> params) {
			exit(c);
		}
	}), EXIT(new Action() {
		@Override
		public void exec(Console c, List<String> params) {
			exit(c);
		}
	}), HELP(new Action() {
		@Override
		public void exec(Console c, List<String> params) {
			c.printf("Commands: [");
			for (Command cm : Command.values()) {
				c.printf(" " + cm.toString());
			}
			c.printf(" ]%n");
		}
	}), MEM(new Action() {
		@Override
		public void exec(Console c, List<String> params) {
			c.printf(LoggerHelper.getMemoryStatistics() + "%n");
		}
	}), MODES(new Action() {
		@Override
		public void exec(Console c, List<String> params) throws Exception {
			GAME_MODE[] rc = GAME_MODE.values();
			c.printf("Game modes: " + LoggerHelper.toString(rc) + "%n");
		}
	}), COMPOSITIONS(new Action() {
		@Override
		public void exec(Console c, List<String> params) throws Exception {
			List<String> rc = ServiceFactory.getInstance().getGameService()
					.getCompositions();
			c.printf("Compositions: #" + rc.size() + " "
					+ LoggerHelper.toString(rc) + "%n");
		}
	}), HISTORY(new Action() {
		@Override
		public void exec(Console c, List<String> params) throws Exception {
			if (gi != null) {
				List<IEvent> events = gi.getGameLogEvents();
				consoleDisplay(c, events);
			}
		}
	}), STATUS(new Action() {
		@Override
		public void exec(Console c, List<String> params) throws Exception {
			if (gi == null) {
				c.printf("No game instance is launched%n");
			} else {
				boolean viewModeShowHidden = (params.size() == 1)
						&& "showHidden".equals(params.get(0));
				if (viewModeShowHidden) {
					c.printf("Board: " + gi.getBoard().toString(true) + "%n");
				}
				consoleDisplay(c, gi);
				if (gi.isGameOver()) {
					c.printf("Winners:%n");
					for (ICharacter w : gi.getWinners()) {
						// TODO explicit winning player(s) from winning
						// character(s)
						c.printf(w + "%n");
					}
				}
			}
		}
	}), STARTGAME(new Action() {
		@Override
		public void exec(Console c, List<String> params) throws Exception {
			List<String> rc = ServiceFactory.getInstance().getGameService()
					.getCompositions();
			if (params.size() == 3) {
				String composition = params.get(0);
				if (!rc.contains(composition)) {
					c.printf("Compositions: #" + rc.size() + " " + rc + "%n");
					c.printf("Undefined composition: " + composition
							+ ", check valid values%n");
					return;
				}

				GAME_MODE mode = GAME_MODE.valueOf(params.get(1));
				int countPlayers = Integer.parseInt(params.get(2));

				List<String> players = new ArrayList<String>();
				for (int j = 0; j < countPlayers; j++) {
					players.add("P" + j);
				}
				gi = ServiceFactory.getInstance().getGameService()
						.startGameInstance(composition, mode, players);
				c.printf("Started a " + mode + " game #" + countPlayers
						+ " player(s) with #" + gi.getMaxCharacter()
						+ " characters%n");
				consoleDisplay(c, gi);
			} else {
				c.printf("Usage: STARTGAME composition mode #players%n");
			}
		}
	}), PLAYEXTRA(new Action() {
		@Override
		public void exec(Console c, List<String> params) throws Exception {
			if (gi == null) {
				c.printf("No game instance is launched%n");
				return;
			}

			CHARACTER_ACTION ca = null;
			try {
				ca = CHARACTER_ACTION.valueOf(params.get(0).toUpperCase());
			} catch (Exception e) {
				c.printf("Usage: PLAY CONTROL|LOOK options..%n");
				return;
			}

			List<EXPECTED_RESPONSE> expectedResponses = new ArrayList<EXPECTED_RESPONSE>();
			boolean playCompleted = false;
			int eventIndex = gi.getGameLogEvents().size();

			switch (ca) {
			case CONTROL:
				expectedResponses.add(EXPECTED_RESPONSE.AllControlMove);

				// TODO
				break;
			case LOOK:
				expectedResponses.add(EXPECTED_RESPONSE.AllHiddenRoomPosition);

				// TODO
				break;
			default:
				// only EXTRA LOOK or CONTROL are allowed
				c.printf("Usage: PLAY CONTROL|LOOK options..%n");
				return;
			}

			if (playCompleted) {
				if (gi.getGameLogEvents().size() > eventIndex) {
					consoleDisplay(
							c,
							gi.getGameLogEvents().subList(eventIndex,
									gi.getGameLogEvents().size()));
				}
				consoleDisplay(c, gi);
			} else {
				showNextExpectedResponse(c, expectedResponses);
				c.printf("Usage: " + ca);
				for (EXPECTED_RESPONSE r : expectedResponses) {
					c.printf(" " + r);
				}
				c.printf("%n");
			}
		}
	}), PLAY(new Action() {
		@Override
		public void exec(Console c, List<String> params) throws Exception {
			if (gi == null) {
				c.printf("No game instance is launched%n");
				return;
			}

			CHARACTER_ACTION ca = null;
			try {
				ca = CHARACTER_ACTION.valueOf(params.get(0).toUpperCase());
			} catch (Exception e) {
				c.printf("Usage: PLAY CONTROL|LOOK|MOVE|PUSH options..%n");
				return;
			}

			// check if room & previous actions in this turn allow the
			// action
			IRoom room = gi.getCurrentCharacter().getRoomIn();
			List<CHARACTER_ACTION> aas = gi.availableCharacterActions();
			if (!aas.contains(ca)) {
				c.printf("Action " + ca
						+ " is not allowed this turn or in this room: " + room
						+ "%n");
				return;
			}

			List<EXPECTED_RESPONSE> expectedResponses = new ArrayList<EXPECTED_RESPONSE>();
			boolean playCompleted = false;
			int eventIndex = gi.getGameLogEvents().size();

			switch (ca) {
			// requires ControlMove
			case CONTROL:
				expectedResponses.add(EXPECTED_RESPONSE.ControlMove);

				if (params.size() == 2) {
					List<ControlMove> vcms = gi.getBoard()
							.getValidControlMoves(room.getRoomPosition());
					int i = Integer.parseInt(params.get(1));

					// TODO pass if size() == 0 && i == 0
					ControlMove cm = vcms.get(i);
					CharacterActionResponse car = gi.executeCharacterAction(ca,
							null, null, cm);
					consoleDisplay(c, car);
					playCompleted = true;
				}
				break;

			// requires RoomPosition
			case LOOK:
				expectedResponses.add(EXPECTED_RESPONSE.RoomPositionForLook);

				if (params.size() == 2) {
					room = gi.getCurrentCharacter().getRoomIn();
					List<RoomPosition> arps = gi.getBoard()
							.getAdjacentRoomPositions(room, false);
					int i = Integer.parseInt(params.get(1));

					// TODO pass if size() == 0 && i == 0
					RoomPosition rp = arps.get(i);
					CharacterActionResponse car = gi.executeCharacterAction(ca,
							rp, null, null);
					consoleDisplay(c, car);
					playCompleted = true;
				}
				break;

			// requires RoomPosition
			case MOVE:
				expectedResponses.add(EXPECTED_RESPONSE.RoomPositionForMove);

				if (params.size() == 2) {
					room = gi.getCurrentCharacter().getRoomIn();
					List<RoomPosition> arps = gi.getBoard()
							.getAdjacentRoomPositions(room, true);
					int i = Integer.parseInt(params.get(1));

					// TODO pass if size() == 0 && i == 0
					RoomPosition rp = arps.get(i);

					CharacterActionResponse car = gi.executeCharacterAction(ca,
							rp, null, null);
					consoleDisplay(c, car);
					playCompleted = true;
				}
				break;

			// requires RoomPosition, ICharacter
			case PUSH:
				expectedResponses.add(EXPECTED_RESPONSE.RoomPositionForMove);
				expectedResponses.add(EXPECTED_RESPONSE.Character);

				if (params.size() == 3) {
					room = gi.getCurrentCharacter().getRoomIn();
					List<ICharacter> cls = gi.getOtherCharactersInRoomObjects(
							room, gi.getCurrentCharacter());
					List<RoomPosition> arps = gi.getBoard()
							.getAdjacentRoomPositions(room, true);
					// TODO pass if 1 of size() == 0 && corresponding index
					// == 0
					int ir = Integer.parseInt(params.get(1));
					RoomPosition rp = arps.get(ir);
					int ic = Integer.parseInt(params.get(2));
					ICharacter cp = cls.get(ic);

					CharacterActionResponse car = gi.executeCharacterAction(ca,
							rp, cp, null);
					consoleDisplay(c, car);
					playCompleted = true;
				}
				break;
			}

			if (playCompleted) {
				if (gi.getGameLogEvents().size() > eventIndex) {
					consoleDisplay(
							c,
							gi.getGameLogEvents().subList(eventIndex,
									gi.getGameLogEvents().size()));
				}
				consoleDisplay(c, gi);
			} else {
				showNextExpectedResponse(c, expectedResponses);
				c.printf("Usage: " + ca);
				for (EXPECTED_RESPONSE r : expectedResponses) {
					c.printf(" " + r);
				}
				c.printf("%n");
			}
		}
	}), RT(new Action() {
		@Override
		public void exec(Console c, List<String> params) {
			if (gi == null) {
				c.printf("No game instance is launched%n");
				return;
			}
			if (!gi.getCurrentCharacter().isReminderTokenUsable()) {
				c.printf("No ReminderToken%n");
				return;
			}

			int eventIndex = gi.getGameLogEvents().size();
			List<EXPECTED_RESPONSE> expectedResponses = new ArrayList<EXPECTED_RESPONSE>();
			boolean playCompleted = false;

			expectedResponses.add(EXPECTED_RESPONSE.AllHiddenRoomPosition);

			if (params.size() == 1) {
				List<RoomPosition> arps = gi.getBoard()
						.getHiddenRoomPositions();
				int i = Integer.parseInt(params.get(0));

				if (arps.size() > 0) {
					RoomPosition rp = arps.get(i);
					CharacterActionResponse car = gi.playReminderToken(rp);
					consoleDisplay(c, car);
				}
				playCompleted = true;
			}

			if (playCompleted) {
				if (gi.getGameLogEvents().size() > eventIndex) {
					consoleDisplay(
							c,
							gi.getGameLogEvents().subList(eventIndex,
									gi.getGameLogEvents().size()));
				}
				consoleDisplay(c, gi);
			} else {
				showNextExpectedResponse(c, expectedResponses);
				c.printf("Usage: ");
				for (EXPECTED_RESPONSE r : expectedResponses) {
					c.printf(" " + r);
				}
				c.printf("%n");
			}
		}
	}), PASS(new Action() {
		@Override
		public void exec(Console c, List<String> params) {
			if (gi == null) {
				c.printf("No game instance is launched%n");
				return;
			}

			int eventIndex = gi.getGameLogEvents().size();

			CharacterActionResponse car = gi.passCharacterAction();
			consoleDisplay(c, car);

			if (gi.getGameLogEvents().size() > eventIndex) {
				consoleDisplay(
						c,
						gi.getGameLogEvents().subList(eventIndex,
								gi.getGameLogEvents().size()));
			}
			consoleDisplay(c, gi);
		}
	}), END(new Action() {
		@Override
		public void exec(Console c, List<String> params) throws Exception {
			int eventIndex = gi.getGameLogEvents().size();

			if (!gi.endCharacterActionPhase())
				throw new Exception("Character must play!");

			if (gi.getGameLogEvents().size() > eventIndex) {
				consoleDisplay(
						c,
						gi.getGameLogEvents().subList(eventIndex,
								gi.getGameLogEvents().size()));
			}
			consoleDisplay(c, gi);
		}
	}), EVENTS(new Action() {
		@Override
		public void exec(Console c, List<String> params) throws Exception {
			consoleDisplay(c, gi.getCheckEvents());
			c.printf("GameStatus: " + gi.getCurrentStateTimeStamp() + " "
					+ gi.getCurrentCharacter().getName() + ":"
					+ gi.getCurrentCharacterStatus() + "%n");
		}
	});

	private enum EXPECTED_RESPONSE {
		ControlMove, RoomPositionForLook, RoomPositionForMove, Character, AllHiddenRoomPosition, AllControlMove
	}

	private interface Action {
		public void exec(Console c, List<String> params) throws Exception;
	}

	public interface Listener {
		public void exception(Exception e);
	}

	private Action action;

	private Command(Action a) {
		this.action = a;
	}

	public void exec(final Console c, final List<String> params,
			final Listener l) {
		try {
			action.exec(c, params);
		} catch (Exception e) {
			l.exception(e);
		}
	}

	protected static void consoleDisplay(Console c, List<IEvent> events) {
		for (int i = 0; i < events.size(); i++) {
			IEvent event = events.get(i);
			c.printf("#" + ((i < 10) ? " " : "") + i + " valid:"
					+ event.isValid() + " " + event.getType() + " @ "
					+ event.getTimeStamp() + "%n");
			if (event.getCharacter() != null) {
				c.printf("    " + event.getCharacter() + "%n");
			}
			if (event.getRoom() != null) {
				c.printf("    " + event.getRoom() + "%n");
			}
		}
	}

	protected static void consoleDisplay(Console c, CharacterActionResponse car) {
		c.printf(car + "%n");
	}

	protected static void consoleDisplay(Console c, IGameInstance gi) {
		c.printf(gi + "%n");
		if (gi.isGameOver()) {
			c.printf("** GAME OVER **%n");
		} else {
			c.printf("GameStatus: " + gi.getCurrentStateTimeStamp() + " "
					+ gi.getCurrentCharacter().getName() + ":"
					+ gi.getCurrentCharacterStatus() + "%n");
		}
	}

	protected static void exit(Console c) {
		try {
			ServiceFactory.getInstance().releaseSWService();
		} catch (Exception e) {
			c.printf(LoggerHelper.getStackTrace(e) + "%n");
		}
		c.printf("Bye%n");
		System.exit(0);
	}

	private static void showNextExpectedResponse(Console c,
			List<EXPECTED_RESPONSE> expectedResponses) {
		for (EXPECTED_RESPONSE expectedResponse : expectedResponses) {
			IRoom room;
			switch (expectedResponse) {
			case Character:
				room = gi.getCurrentCharacter().getRoomIn();
				List<ICharacter> cls = gi.getOtherCharactersInRoomObjects(room,
						gi.getCurrentCharacter());

				if (cls.isEmpty()) {
					c.printf("Character 0: no accessible Character%n");
				}
				for (int i = 0; i < cls.size(); i++) {
					c.printf("Character " + i + ": " + cls.get(i) + "%n");
				}
				break;
			case ControlMove:
				// list allowed ControlMove's
				room = gi.getCurrentCharacter().getRoomIn();
				List<ControlMove> vcms = gi.getBoard().getValidControlMoves(
						room.getRoomPosition());

				if (vcms.isEmpty()) {
					c.printf("ControlMove 0: no possible ControlMove%n");
				}
				for (int i = 0; i < vcms.size(); i++) {
					c.printf("ControlMove " + i + ": " + vcms.get(i) + "%n");
				}
				break;
			case RoomPositionForLook:
				// list adjacent RoomPosition's
				room = gi.getCurrentCharacter().getRoomIn();
				List<RoomPosition> arps = gi.getBoard()
						.getAdjacentRoomPositions(room, false);

				if (arps.isEmpty()) {
					c.printf("RoomPosition 0: no adjacent RoomPosition%n");
				}
				for (int i = 0; i < arps.size(); i++) {
					c.printf("RoomPosition " + i + ": " + arps.get(i) + "%n");
				}
				break;
			case RoomPositionForMove:
				// list adjacent RoomPosition's
				room = gi.getCurrentCharacter().getRoomIn();
				arps = gi.getBoard().getAdjacentRoomPositions(room, true);

				if (arps.isEmpty()) {
					c.printf("RoomPosition 0: no adjacent RoomPosition%n");
				}
				for (int i = 0; i < arps.size(); i++) {
					c.printf("RoomPosition " + i + ": " + arps.get(i) + "%n");
				}
				break;
			case AllControlMove:
				List<ControlMove> acms = gi.getBoard().getValidControlMoves();

				if (acms.isEmpty()) {
					c.printf("RoomPosition 0: no valid ControlMove%n");
				}
				for (int i = 0; i < acms.size(); i++) {
					c.printf("ControlMove " + i + ": " + acms.get(i) + "%n");
				}
				break;
			case AllHiddenRoomPosition:
				// list hidden RoomPosition's
				arps = gi.getBoard().getHiddenRoomPositions();

				if (arps.isEmpty()) {
					c.printf("RoomPosition 0: no hidden RoomPosition%n");
				}
				for (int i = 0; i < arps.size(); i++) {
					c.printf("RoomPosition " + i + ": " + arps.get(i) + "%n");
				}
				break;
			default:
				break;
			}
		}
	}

	// the console is able to play a single GameInstance at the time
	private static IGameInstance gi;
}
